﻿using System;
using System.Collections.Generic;
using ParserGenerator.Exceptions;
using ParserGenerator.Input;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.GrammarConstruction
{
    [Serializable]
    public sealed class LexicalPrecedenceTable
    {
        private readonly Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, LexicalPrecedence> _lessByIdentity;
        private readonly Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, LexicalPrecedence> _lessByLength;
        private readonly Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, LexicalPrecedence> _preferLonger;
        private readonly Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, LexicalPrecedence> _preferShorter;

        internal LexicalPrecedenceTable(GrammarDeclaration grammar, GrammarTerminalTable tokens)
        {
            if (grammar == null)
                throw new ArgumentNullException(nameof(grammar));
            if (tokens == null)
                throw new ArgumentNullException(nameof(tokens));
            _lessByIdentity = new Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, LexicalPrecedence>();
            _lessByLength = new Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, LexicalPrecedence>();
            _preferLonger = new Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, LexicalPrecedence>();
            _preferShorter = new Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, LexicalPrecedence>();
            foreach (Directive d in grammar.Directives.Items)
                if (d.IsLexicalPrecedence)
                    Define(d.AsLexicalPrecedence.Declaration, tokens);
        }

        internal void Define(LexicalPrecedenceDeclaration n, GrammarTerminalTable tokens)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (tokens == null)
                throw new ArgumentNullException(nameof(tokens));
            foreach (LexicalPrecedenceDeclarator item in n.Items)
            {
                GrammarTerminalSymbol left = tokens.Lookup(item.Left);
                GrammarTerminalSymbol right = tokens.Lookup(item.Right);
                switch (item.Op)
                {
                    case LexicalPrecedenceOperator.LessTilde:
                        DefineLessByIdentity(left, right, item.Context);
                        DefinePreferLonger(left, right, item.Context);
                        break;
                    case LexicalPrecedenceOperator.LessMinus:
                        DefineLessByIdentity(left, right, item.Context);
                        break;
                    case LexicalPrecedenceOperator.MinusTilde:
                        DefinePreferLonger(left, right, item.Context);
                        break;
                    case LexicalPrecedenceOperator.LessLess:
                        DefineLessByIdentity(left, right, item.Context);
                        DefineLessByLength(left, right, item.Context);
                        break;
                    case LexicalPrecedenceOperator.LessS:
                        DefineLessByIdentity(left, right, item.Context);
                        DefinePreferShorter(left, right, item.Context);
                        break;
                    case LexicalPrecedenceOperator.MinusLess:
                        DefineLessByLength(left, right, item.Context);
                        break;
                    case LexicalPrecedenceOperator.MinusS:
                        DefinePreferShorter(left, right, item.Context);
                        break;
                    default:
                        throw new InvalidOperationException(SR.InvalidLexicalPrecedenceOperator);
                }
            }
        }

        private void DefineLessByIdentity(GrammarTerminalSymbol left, GrammarTerminalSymbol right, SourceContext context)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            LexicalPrecedence defined = TryLookupLessByIdentity(left, right);
            if (defined != null)
            {
                string message = string.Format(SR.LexicalPrecedenceDefined, defined);
                GrammarErrorException e = new GrammarErrorException(message);
                e.AddLocation(defined.Context);
                e.AddLocation(context);
                throw e;
            }
            LexicalPrecedence value = new LexicalPrecedence(left, LexicalPrecedenceOperator.LessMinus, right, context);
            if (left == right)
            {
                string message = string.Format(SR.SameOperandsInLexicalPrecedence, value);
                GrammarErrorException e = new GrammarErrorException(message);
                e.AddLocation(context);
                throw e;
            }
            LexicalPrecedence reverse = TryLookupLessByIdentity(right, left);
            if (reverse != null)
            {
                string message = string.Format(SR.ReverseLexicalPrecedenceDefined, reverse, value);
                GrammarErrorException e = new GrammarErrorException(message);
                e.AddLocation(reverse.Context);
                e.AddLocation(context);
                throw e;
            }
            var key = new KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>(left, right);
            _lessByIdentity.Add(key, value);
        }

        private void DefineLessByLength(GrammarTerminalSymbol left, GrammarTerminalSymbol right, SourceContext context)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            LexicalPrecedence defined = TryLookupLessByLength(left, right);
            if (defined != null)
            {
                string message = string.Format(SR.LexicalPrecedenceDefined, defined);
                GrammarErrorException e = new GrammarErrorException(message);
                e.AddLocation(defined.Context);
                e.AddLocation(context);
                throw e;
            }
            LexicalPrecedence value = new LexicalPrecedence(left, LexicalPrecedenceOperator.MinusLess, right, context);
            if (left == right)
            {
                string message = string.Format(SR.SameOperandsInLexicalPrecedence, value);
                GrammarErrorException e = new GrammarErrorException(message);
                e.AddLocation(context);
                throw e;
            }
            LexicalPrecedence reverse = TryLookupLessByLength(right, left);
            if (reverse != null)
            {
                string message = string.Format(SR.ReverseLexicalPrecedenceDefined, reverse, value);
                GrammarErrorException e = new GrammarErrorException(message);
                e.AddLocation(reverse.Context);
                e.AddLocation(context);
                throw e;
            }
            var key = new KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>(left, right);
            _lessByLength.Add(key, value);
        }

        private void DefinePreferLonger(GrammarTerminalSymbol left, GrammarTerminalSymbol right, SourceContext context)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            if (left.Number > right.Number)
            {
                GrammarTerminalSymbol t = left;
                left = right;
                right = t;
            }
            LexicalPrecedence defined = TryLookupPreferLonger(left, right);
            if (defined != null)
            {
                string message = string.Format(SR.LexicalPrecedenceDefined, defined);
                GrammarErrorException e = new GrammarErrorException(message);
                e.AddLocation(defined.Context);
                e.AddLocation(context);
                throw e;
            }
            LexicalPrecedence value = new LexicalPrecedence(left, LexicalPrecedenceOperator.MinusTilde, right, context);
            LexicalPrecedence reverse = TryLookupPreferShorter(right, left);
            if (reverse != null)
            {
                string message = string.Format(SR.ReverseLexicalPrecedenceDefined, reverse, value);
                GrammarErrorException e = new GrammarErrorException(message);
                e.AddLocation(reverse.Context);
                e.AddLocation(context);
                throw e;
            }
            var key = new KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>(left, right);
            _preferLonger.Add(key, value);
        }

        private void DefinePreferShorter(GrammarTerminalSymbol left, GrammarTerminalSymbol right, SourceContext context)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            if (left.Number > right.Number)
            {
                GrammarTerminalSymbol t = left;
                left = right;
                right = t;
            }
            LexicalPrecedence defined = TryLookupPreferShorter(left, right);
            if (defined != null)
            {
                string message = string.Format(SR.LexicalPrecedenceDefined, defined);
                GrammarErrorException e = new GrammarErrorException(message);
                e.AddLocation(defined.Context);
                e.AddLocation(context);
                throw e;
            }
            LexicalPrecedence value = new LexicalPrecedence(left, LexicalPrecedenceOperator.MinusTilde, right, context);
            LexicalPrecedence reverse = TryLookupPreferLonger(right, left);
            if (reverse != null)
            {
                string message = string.Format(SR.ReverseLexicalPrecedenceDefined, reverse, value);
                GrammarErrorException e = new GrammarErrorException(message);
                e.AddLocation(reverse.Context);
                e.AddLocation(context);
                throw e;
            }
            var key = new KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>(left, right);
            _preferShorter.Add(key, value);
        }

        private LexicalPrecedence TryLookupLessByIdentity(GrammarTerminalSymbol left, GrammarTerminalSymbol right)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            var key = new KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>(left, right);
            LexicalPrecedence value;
            _lessByIdentity.TryGetValue(key, out value);
            return value;
        }

        private LexicalPrecedence TryLookupLessByLength(GrammarTerminalSymbol left, GrammarTerminalSymbol right)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            var key = new KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>(left, right);
            LexicalPrecedence value;
            _lessByLength.TryGetValue(key, out value);
            return value;
        }

        private LexicalPrecedence TryLookupPreferLonger(GrammarTerminalSymbol left, GrammarTerminalSymbol right)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            if (left.Number > right.Number)
            {
                GrammarTerminalSymbol t = left;
                left = right;
                right = t;
            }
            var key = new KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>(left, right);
            LexicalPrecedence value;
            _preferLonger.TryGetValue(key, out value);
            return value;
        }

        private LexicalPrecedence TryLookupPreferShorter(GrammarTerminalSymbol left, GrammarTerminalSymbol right)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            if (left.Number > right.Number)
            {
                GrammarTerminalSymbol t = left;
                left = right;
                right = t;
            }
            var key = new KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>(left, right);
            LexicalPrecedence value;
            _preferShorter.TryGetValue(key, out value);
            return value;
        }

        private bool SatisfyLessByIdentity(GrammarTerminalSymbol left, GrammarTerminalSymbol right)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            LexicalPrecedence value = TryLookupLessByIdentity(left, right);
            return value != null;
        }

        private bool SatisfyLessByLength(GrammarTerminalSymbol left, GrammarTerminalSymbol right)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            LexicalPrecedence value = TryLookupLessByLength(left, right);
            return value != null;
        }

        private bool SatisfyPreferLonger(GrammarTerminalSymbol left, GrammarTerminalSymbol right)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            LexicalPrecedence value = TryLookupPreferLonger(left, right);
            if (value != null)
                return true;
            if (left != right)
                return false;
            value = TryLookupPreferShorter(left, left);
            return value == null;
        }

        private bool SatisfyPreferShorter(GrammarTerminalSymbol left, GrammarTerminalSymbol right)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            LexicalPrecedence value = TryLookupPreferShorter(left, right);
            return value != null;
        }

        public bool CheckShorterToken(GrammarTerminalSymbol shorterToken, HashSet<GrammarTerminalSymbol> longerTokens)
        {
            if (shorterToken == null)
                throw new ArgumentNullException(nameof(shorterToken));
            if (longerTokens == null)
                throw new ArgumentNullException(nameof(longerTokens));
            foreach (GrammarTerminalSymbol t in longerTokens)
                if (!SatisfyLessByLength(t, shorterToken) && !SatisfyPreferShorter(t, shorterToken))
                    return false;
            return true;
        }

        private bool CheckLongerToken(GrammarTerminalSymbol longerToken, HashSet<GrammarTerminalSymbol> shorterTokens)
        {
            if (longerToken == null)
                throw new ArgumentNullException(nameof(longerToken));
            if (shorterTokens == null)
                throw new ArgumentNullException(nameof(shorterTokens));
            foreach (GrammarTerminalSymbol t in shorterTokens)
                if (!SatisfyLessByLength(t, longerToken) && !SatisfyPreferLonger(t, longerToken))
                    return false;
            return true;
        }

        private GrammarTerminalSymbol ChooseTokenByIdentity(HashSet<GrammarTerminalSymbol> tokens)
        {
            if (tokens == null)
                throw new ArgumentNullException(nameof(tokens));
            GrammarTerminalSymbol result = null;
            foreach (GrammarTerminalSymbol token in tokens)
            {
                bool valid = true;
                foreach (GrammarTerminalSymbol t in tokens)
                {
                    if (t != token && !SatisfyLessByIdentity(t, token))
                    {
                        valid = false;
                        break;
                    }
                }
                if (valid)
                {
                    if (result == null)
                        result = token;
                    else
                        return null;
                }
            }
            return result;
        }

        public GrammarTerminalSymbol ChooseLongerToken(HashSet<GrammarTerminalSymbol> shorterTokens, HashSet<GrammarTerminalSymbol> longerTokens)
        {
            if (shorterTokens == null)
                throw new ArgumentNullException(nameof(shorterTokens));
            if (longerTokens == null)
                throw new ArgumentNullException(nameof(longerTokens));
            GrammarTerminalSymbol longerToken = ChooseTokenByIdentity(longerTokens);
            if (longerToken != null && CheckLongerToken(longerToken, shorterTokens))
                return longerToken;
            return null;
        }
    }
}
